#include <ctime>
#include <queue>
#include <map>
#include <set>
#include <list>
#include <iostream>
#include <cstring>
#include <vector>
#include "process.cpp"
#include "scheduler.h"
#include "limits.h"


using namespace std;

Scheduler::Scheduler() {
  // For Analysis
  PageFaultCount = 0;
  SwitchCount = 0;

  cout << "Initializing MMU, TLB, PT" << endl;
  RR_Time = 0;
  currentProcess = NULL;
  pt = new PageTable(); // global page-table
  tlb = new TLB(); // global TLB
  mm = new MM(); // main memory
  mmu = new MMU(pt, tlb, mm); // global MMU
}

// round-robin scheduler 
int Scheduler::run(int tick) {
  SchedulerReturn sr;
  ProcessReturn pr;
  sr.return_code = SRET_ERROR;
  pr.ret = PRET_SUCCESS;

  checkBlockedList(tick); // remove items from blocked list depending on current tick count;
  
  if(contextSwitchTime > 0){
    contextSwitchTime--;
    if(contextSwitchTime == 5){
       sr.return_code = SRET_CS_PF;
    }
    else{
       sr.return_code = SRET_SUCCESS;
    } 
    cout << "Context Switch In Progress" << endl; 
    return sr.return_code;
  }

 // currently not running process and no active or waiting processes
  if(currentProcess == NULL && ActiveProcesses.size() == 0 && WaitingProcesses.size() == 0){
    sr.return_code = SRET_NO_PROCESSES;
  }

  // currently not running process and no active processes
  else if(currentProcess == NULL && ActiveProcesses.size() == 0){
    sr.return_code = SRET_NO_ACTIVE_PROCESSES;
  }	
  
  // there are processes available to run
  else{
    
    // if no process currently running and active process available, load next active process in queue
    if(currentProcess == NULL && ActiveProcesses.size() > 0){
      currentProcess = ActiveProcesses.find(ActiveQueue.front())->second;

      //cout << "currentPid before load next item: " << currentPid << endl;

      prevProcess = currentPid;

      // copy in page table
      *pt = (*(currentProcess->returnPageTable()));
      currentPid = ActiveQueue.front();
      ActiveQueue.pop();
      RR_Time = TIME_SLICE;
      cout << "Loading next item in queue: pid = " << currentPid << endl;

      if(prevProcess != currentPid){
        cout << "Flush TLB -2" << endl;
        //flush out the tlb
        delete tlb;
        cout << "Flush TLB - Done" << endl;
        tlb = new TLB();
        mmu->setTlb(tlb);
      }

      //cout << "currentPid after load next item: " << currentPid << endl;
    }

    cout << "Current Process: " << currentPid << endl;
    
    // if currently running process used entire time slice, switch for next process in queue, unless only remaining process
    if(currentProcess != NULL && RR_Time <=0){
      if(ActiveProcesses.size() > 1){
        cout << "Context Switch: Old Pid = " << currentPid << endl;
        switchProcesses(tick);
        cout << "                New Pid = " << currentPid << endl;
        contextSwitchTime = 5;
        sr.return_code = SRET_CS_PF;
        return sr.return_code;
      }
      else{
        cout << "Only Active Process: No Context Switch" << endl;
      }
    } 

    // run process
    ProcessReturn pr;
    cout << "Calling currentProcess->run();" << endl;
    int rcode = currentProcess->run(tick);
    cout << "Process Return Code: " << rcode << endl;
 
    pr.ret = rcode;
    // uneventful run
    if(pr.ret == PRET_SUCCESS){
      RR_Time--;

      sr.return_code = SRET_SUCCESS;
    }

    // process terminated
    else if(pr.ret == PRET_END_PROCESS){
      ActiveProcesses.erase(currentPid);
      currentProcess->close();
      delete currentProcess;
      currentProcess = NULL;
      pidSet.erase(currentPid);
      RR_Time = 0;
      sr.return_code = SRET_SUCCESS;
    }

    // notify OS of Page Fault
    else if(pr.ret == PRET_PAGE_FAULT || pr.ret == PRET_PAGE_FAULT_DIRTY){
      WaitingProcesses.insert(pair<int,Process*>(currentPid,currentProcess));
      ActiveProcesses.erase(currentPid);

      // For Analysis
      PageFaultCount++;

      if (pr.ret == PRET_PAGE_FAULT) {
        waitingList.insert(pair<int,int>(currentPid,tick+10)); // wait 10 ticks for I/O to complete
      } else { // pre.ret =1= PRET_PAGE_FAULT_DIRTY
	waitingList.insert(pair<int,int>(currentPid,tick+20)); // wait 20 ticks for I/O to complete
      }

      if(ActiveProcesses.size() > 0){
	// For Analysis
	SwitchCount++;

        cout << "Page Fault: Blocking Process " << currentPid << ": Context Switching" << endl;
        RR_Time = 0;

        // modify ref bits for Lru Approx on context switch
        if(mmu->getPolicy() == 1){ // LRU Approx
           refBitLRUApprox(tick);
        }

        //copy out the page table when the process is context switched. Copying the value and not references.
        currentProcess->setPageTable(pt);  

        cout << "Flush TLB - 3" << endl;
        //flush out the tlb
        delete tlb;
        cout << "Flush TLB - Done" << endl;
        tlb = new TLB();
        mmu->setTlb(tlb);

        currentProcess = NULL;
        contextSwitchTime = 5;
        sr.return_code = SRET_CS_PF;
      }
      else{
        cout << "Page Fault: Blocking Process " << currentPid << ": Only Active Process: No Context Switch" << endl;
        RR_Time = 0;

        // modify ref bits for Lru Approx on context switch
        if(mmu->getPolicy() == 1){ // LRU Approx
           refBitLRUApprox(tick);
        }

        //copy out the page table when the process is context switched. Copying the value and not references.
        currentProcess->setPageTable(pt);  

        currentProcess = NULL;
        sr.return_code = SRET_CS_PF;
      }
    }

    // process indicates error
    else{
      cout << "Something went wrong in process" << endl; 
    }
  }
  return sr.return_code;
}

// push current process to back of active process queue and load process at front of queue
int Scheduler::switchProcesses(int itick) {
  SwitchCount++;  

  // modify ref bits for Lru Approx on context switch
  if(mmu->getPolicy() == 1){ // LRU Approx
     refBitLRUApprox(itick);
  }

  //copy out the page table when the process is context switched. Copying the value and not references.
  currentProcess->setPageTable(pt);  

  cout << "Flush TLB -1" << endl;
  //flush out the tlb
  delete tlb;
  cout << "Flush TLB - Done" << endl;
  tlb = new TLB();
  mmu->setTlb(tlb);

  ActiveQueue.push(currentPid);
  currentProcess = ActiveProcesses.find(ActiveQueue.front())->second;
  currentPid = ActiveQueue.front();
  
  //copy in the values of the pt of current process into the global page table. Again, copy in values and not references.
  *pt = (*(currentProcess->returnPageTable()));
  
  ActiveQueue.pop();
  RR_Time = TIME_SLICE;

  return 0;
}

// remove items from blocklist after indicated tick count
int Scheduler::checkBlockedList(int itick){

  int counter = 0;
  map<int,int>::iterator it;

   for (it=waitingList.begin(); it != waitingList.end(); it++ ){
      if((*it).second <= itick){
          UnblockProcess((*it).first); // unblock all items in map with tick count less than or equal to current tick count
          unblockList.push_back((*it).first); // add to list to delete from map later (can't alter while iterating)
      }
   }

   // delete all items in unblockList from waitingList
   for (counter = 0; counter < unblockList.size(); counter++){
        waitingList.erase(unblockList[counter]);
   }

   // clear unblockList
   unblockList.clear();
}

// allow OS to add processes when created
int Scheduler::AddProcess(int ipid, Process* p) {
    cout << "Adding MMU to PID: " << ipid << endl;
    p->addMMU(mmu);
    ActiveQueue.push(ipid);
    ActiveProcesses.insert(pair<int,Process*>(ipid,p));
}

// allow OS to indicate system call completeion and to unblock process.
int Scheduler::UnblockProcess(int ipid) {
    cout << "Unblocking process: " << ipid << endl;
    ActiveProcesses.insert(pair<int,Process*>(ipid,WaitingProcesses.find(ipid)->second));
    WaitingProcesses.erase(ipid);
    ActiveQueue.push(ipid);
}

void Scheduler::setBaseBound(){
  int frameCounter = 0;
  map<int,Process*>::iterator it;
  int framesPerProc = mmu->getFramesPerProcess();
  int j = 1;

  cout << "FramesPerProcess: " << framesPerProc << endl;

   for (it=ActiveProcesses.begin(); it != ActiveProcesses.end(); it++ ){

      (*it).second->setBase(frameCounter);
      (*it).second->setBound(frameCounter + framesPerProc - 1);

      frameCounter += framesPerProc;

      cout << "Process #:" << j << " Base: " << (*it).second->getBase() << " Bound: " << (*it).second->getBound() << endl;
      j++;
   }
}

void Scheduler::refBitLRUApprox(int itick){
   cout << endl << "Setting Time Field for Referenced Entries in PT" << endl;
   for(counter = 0; counter < NUM_PAGES; counter++){
      if(pt->entries[counter].ref == 1){
          pt->entries[counter].time = itick;
          pt->entries[counter].ref = 0;
      }
   }
}

// select lowest available pid
int Scheduler::getPid() {

  int i;
  int ret_value = -1;

  for(i = 1; i < INT_MAX; i++){
    if((pidSet.insert(i)).second){
       ret_value = i;
       break;
    }
  }
  return ret_value;
}
